package com.ac.scum.log.web.model.dashboard;

import com.ac.common.core.exception.BaseException;
import com.ac.scum.log.bean.Player;
import com.ac.scum.log.web.model.ftp.mapper.ServerFTPLogChatMapper;
import com.ac.scum.log.web.model.ftp.mapper.ServerFTPLogKillMapper;
import com.ac.scum.log.web.model.ftp.mapper.ServerFTPLogLockMapper;
import com.ac.scum.log.web.model.ftp.mapper.ServerFTPLogLoginMapper;
import com.ac.scum.log.web.model.player.mapper.PlayerMapper;
import com.ac.scum.log.web.utils.user.CurrentUserUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

@Slf4j
@Service
@EnableAsync
@AllArgsConstructor
public class DashboardService {
    private final PlayerMapper playerMapper;
    private final ServerFTPLogChatMapper serverFTPLogChatMapper;
    private final ServerFTPLogLockMapper serverFTPLogLockMapper;
    private final ServerFTPLogKillMapper serverFTPLogKillMapper;
    private final ServerFTPLogLoginMapper serverFTPLogLoginMapper;
    private final RedisTemplate<String, Dashboard> redisTemplate;

    private static final String DASHBOARD_YESTERDAY_KEY = "dashboard:yesterday:%s";

    @SuppressWarnings("DuplicatedCode")
    public Dashboard load() throws BaseException, DataAccessException, ExecutionException, InterruptedException {
        Long serverId = CurrentUserUtil.getCurrentServerId();
        if (serverId == null) {
            throw new IllegalArgumentException("Server ID cannot be null.");
        }

        LocalDateTime todayStartTime = getLocalDateTimeAtStartOfDay();
        LocalDateTime todayEndTime = getLocalDateTimeAtEndOfDay();
        LocalDateTime yesterdayStartTime = todayStartTime.minusDays(1);
        LocalDateTime yesterdayEndTime = todayEndTime.minusDays(1);

        Dashboard dashboard = redisTemplate.opsForValue().get(String.format(DASHBOARD_YESTERDAY_KEY, serverId));
        dashboard = dashboard == null ? new Dashboard() : dashboard;

        boolean needWriteToCache = false;

        CompletableFuture<Integer> todayChatCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogChatMapper.countByDate(serverId, todayStartTime, todayEndTime));
        CompletableFuture<List<Player>> todayNewPlayersFuture = CompletableFuture.supplyAsync(() -> playerMapper.findPlayersByDate(serverId, todayStartTime, todayEndTime));
        CompletableFuture<Integer> todayNewPlayerCountFuture = CompletableFuture.supplyAsync(() -> playerMapper.findNewPlayerCountByDate(serverId, todayStartTime, todayEndTime));
        CompletableFuture<Integer> todayLoginCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLoginMapper.countLoginCountByDate(serverId, todayStartTime, todayEndTime));
        CompletableFuture<Integer> todayKillCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogKillMapper.countKillCountByDateAndType(serverId, todayStartTime, todayEndTime, 2));
        CompletableFuture<Integer> todayEventKillCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogKillMapper.countKillCountByDateAndType(serverId, todayStartTime, todayEndTime, 1));
        CompletableFuture<Integer> todaySystemLockCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLockMapper.countSystemLockByDateAndIsSysLockFlag(serverId, todayStartTime, todayEndTime, Boolean.TRUE));
        CompletableFuture<Integer> todayPlayerLockCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLockMapper.countSystemLockByDateAndIsSysLockFlag(serverId, todayStartTime, todayEndTime, Boolean.FALSE));


        if (dashboard.getYesterdayChatCount() == null) {
            needWriteToCache = true;
            log.info("不走缓存");
            CompletableFuture<Integer> yesterdayChatCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogChatMapper.countByDate(serverId, yesterdayStartTime, yesterdayEndTime));
            CompletableFuture<List<Player>> yesterdayNewPlayersFuture = CompletableFuture.supplyAsync(() -> playerMapper.findPlayersByDate(serverId, yesterdayStartTime, yesterdayEndTime));
            CompletableFuture<Integer> yesterdayNewPlayerCountFuture = CompletableFuture.supplyAsync(() -> playerMapper.findNewPlayerCountByDate(serverId, yesterdayStartTime, yesterdayEndTime));
            CompletableFuture<Integer> yesterdayLoginCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLoginMapper.countLoginCountByDate(serverId, yesterdayStartTime, yesterdayEndTime));
            CompletableFuture<Integer> yesterdayKillCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogKillMapper.countKillCountByDateAndType(serverId, yesterdayStartTime, yesterdayEndTime, 2));
            CompletableFuture<Integer> yesterdayEventKillCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogKillMapper.countKillCountByDateAndType(serverId, yesterdayStartTime, yesterdayEndTime, 1));
            CompletableFuture<List<DashboardDateItem<Long>>> yesterdayPlayerLockItemsFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLockMapper.yesterdayEveryOpenPlayers(serverId, yesterdayStartTime, yesterdayEndTime));
            CompletableFuture<Integer> yesterdaySystemLockCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLockMapper.countSystemLockByDateAndIsSysLockFlag(serverId, yesterdayStartTime, yesterdayEndTime, Boolean.TRUE));
            CompletableFuture<Integer> yesterdayPlayerLockCountFuture = CompletableFuture.supplyAsync(() -> serverFTPLogLockMapper.countSystemLockByDateAndIsSysLockFlag(serverId, yesterdayStartTime, yesterdayEndTime, Boolean.FALSE));
            CompletableFuture<List<DashboardDateItem<Long>>> yesterdayEveryChatPlayerDateItemsFuture = CompletableFuture.supplyAsync(() -> serverFTPLogChatMapper.yesterdayEveryChatPlayer(serverId, yesterdayStartTime, yesterdayEndTime));

            CompletableFuture.allOf(yesterdayChatCountFuture, yesterdayEveryChatPlayerDateItemsFuture, yesterdaySystemLockCountFuture, yesterdayPlayerLockCountFuture, yesterdayPlayerLockItemsFuture, yesterdayEventKillCountFuture, yesterdayKillCountFuture, yesterdayLoginCountFuture, yesterdayNewPlayerCountFuture, yesterdayNewPlayersFuture).join();
            dashboard.setYesterdayChatCount(yesterdayChatCountFuture.get());
            dashboard.setYesterdayKillCount(yesterdayKillCountFuture.get());
            dashboard.setYesterdayLoginCount(yesterdayLoginCountFuture.get());
            dashboard.setYesterdayNewPlayers(yesterdayNewPlayersFuture.get());
            dashboard.setYesterdayNewPlayerCount(yesterdayNewPlayerCountFuture.get());
            dashboard.setYesterdayEventKillCount(yesterdayEventKillCountFuture.get());
            dashboard.setYesterdaySystemLockCount(yesterdaySystemLockCountFuture.get());
            dashboard.setYesterdayPlayerLockCount(yesterdayPlayerLockCountFuture.get());

            //昨日开玩家锁玩家
            List<DashboardDateItem<Long>> dashboardDateItems = yesterdayPlayerLockItemsFuture.get();
            List<Player> yesterdayOpenLockPlayers = assemblePlayers(dashboardDateItems, serverId);
            List<Integer> yesterdayPlayersLockCountList = dashboardDateItems.stream().map(DashboardDateItem::getCount).collect(Collectors.toList());
            dashboard.setYesterdayOpenPlayerLockPlayers(yesterdayOpenLockPlayers);
            dashboard.setYesterdayOpenPlayerLockCountList(yesterdayPlayersLockCountList);

            //昨日玩家聊天
            List<DashboardDateItem<Long>> yesterdayEveryChatPlayerDateItems = yesterdayEveryChatPlayerDateItemsFuture.get();
            List<Player> yesterdayChatPlayers = assemblePlayers(yesterdayEveryChatPlayerDateItems, serverId);
            List<Integer> yesterdayPlayersChatCountList = yesterdayEveryChatPlayerDateItems.stream().map(DashboardDateItem::getCount).collect(Collectors.toList());
            dashboard.setYesterdayChatPlayers(yesterdayChatPlayers);
            dashboard.setYesterdayPlayersChatCountList(yesterdayPlayersChatCountList);


        }

        CompletableFuture.allOf(todayChatCountFuture, todaySystemLockCountFuture, todayPlayerLockCountFuture, todayEventKillCountFuture, todayKillCountFuture, todayLoginCountFuture, todayNewPlayerCountFuture, todayNewPlayersFuture).join();
        dashboard.setTodayChatCount(todayChatCountFuture.get());
        dashboard.setTodayKillCount(todayKillCountFuture.get());
        dashboard.setTodayNewPlayers(todayNewPlayersFuture.get());
        dashboard.setTodayLoginCount(todayLoginCountFuture.get());
        dashboard.setTodayNewPlayerCount(todayNewPlayerCountFuture.get());
        dashboard.setTodayEventKillCount(todayEventKillCountFuture.get());
        dashboard.setTodayPlayerLockCount(todayPlayerLockCountFuture.get());
        dashboard.setTodaySystemLockCount(todaySystemLockCountFuture.get());

        Dashboard writeCacheDashboard = new Dashboard();
        BeanUtils.copyProperties(dashboard, writeCacheDashboard);
        if (needWriteToCache) {
            writeYesterdayDataToCache(serverId, writeCacheDashboard);
        }
        return dashboard;
    }

    private void writeYesterdayDataToCache(Long serverId, Dashboard dashboard) {

        dashboard.setTodayChatCount(null);
        dashboard.setTodayKillCount(null);
        dashboard.setTodayLoginCount(null);
        dashboard.setTodayNewPlayers(null);
        dashboard.setTodayEventKillCount(null);
        dashboard.setTodayNewPlayerCount(null);
        dashboard.setTodaySystemLockCount(null);
        dashboard.setTodayPlayerLockCount(null);

        String cacheKey = String.format(DASHBOARD_YESTERDAY_KEY, serverId);
        long currentTimestamp = System.currentTimeMillis();
        Date date = new Date(currentTimestamp);
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        calendar.setTime(date);
        calendar.add(Calendar.DATE, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        redisTemplate.opsForValue().set(cacheKey, dashboard);
        redisTemplate.expireAt(cacheKey, calendar.getTime());
    }

    @NotNull
    private List<Player> assemblePlayers(List<DashboardDateItem<Long>> yesterdayEveryChatPlayerDateItems, Long serverId) {
        if (!yesterdayEveryChatPlayerDateItems.isEmpty()) {
            List<Long> steamIds = yesterdayEveryChatPlayerDateItems.stream().map(DashboardDateItem::getData).collect(Collectors.toList());
            List<Player> players = playerMapper.findPlayerByServerIdAndSteamIds(serverId, steamIds);
            return yesterdayEveryChatPlayerDateItems.stream().map(yesterdayEveryChatPlayerDateItem -> players.stream().filter(player -> player.getSteamId().equals(yesterdayEveryChatPlayerDateItem.getData())).findFirst().orElse(null)).filter(Objects::nonNull).collect(Collectors.toList());

        }
        return Lists.newArrayList();
    }

    private LocalDateTime getLocalDateTimeAtStartOfDay() {
        return LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(1);
    }

    private LocalDateTime getLocalDateTimeAtEndOfDay() {
        return LocalDateTime.now().toLocalDate().atTime(23, 59, 59, 999999999);
    }
}
